package com.sh.data.engine.domain.api.run;

import com.google.common.collect.Lists;
import com.sh.data.engine.common.enumDefinition.DSType;
import com.sh.data.engine.common.enumDefinition.DataApiCallStatusEnum;
import com.sh.data.engine.common.exception.BusinessException;
import com.sh.data.engine.domain.api.model.domain.DataApiCallRecordDomain;
import com.sh.data.engine.domain.api.model.domain.DataApiTaskResultDomain;
import com.sh.data.engine.domain.api.model.domain.rest.DataApiIndexTaskConfigInfoDomain;
import com.sh.data.engine.domain.api.model.domain.rest.DataApiTaskParamDomain;
import com.sh.data.engine.domain.api.model.param.DataApiCommomParam;
import com.sh.data.engine.domain.api.util.DataApiUtil;
import com.sh.data.engine.domain.base.model.PageResult;
import com.sh.data.engine.domain.base.model.enums.Database;
import com.sh.data.engine.domain.common.transformer.DataApiParamConverter;
import com.sh.data.engine.domain.shims.sql.builder.QueryBuilder;
import com.sh.data.engine.domain.shims.sql.select.*;
import com.sh.data.engine.domain.util.JDBCUtil;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.time.DateFormatUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;

import java.sql.Connection;
import java.sql.DriverManager;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author: zigui.zdf
 * @description:
 * @date: 2020/11/11 15:21
 */
public class DataApiIndexTaskExecutor extends DataApiTaskExecutor<Map<String, String>> {

    private DataApiIndexTaskConfigInfoDomain guideTaskConfigInfo;

    private Integer pageable;

    private List<DataApiTaskParamDomain> requestParams;

    private List<DataApiTaskParamDomain> responseParams;

    private QueryBuilder queryBuilder = null;

    private String dsType;
    private String dsLink;
    private String username;
    private String password;
    private String tableName;

    protected DataApiIndexTaskExecutor(
        DataApiCallRecordDomain apiRecord,
        DataApiIndexTaskConfigInfoDomain taskConfig,
        Map<String, String> params,
        Long id) {
        super(apiRecord, taskConfig, params, id);

        this.guideTaskConfigInfo = taskConfig;
        this.pageable = taskConfig.getPageable();

        this.requestParams =
            taskConfig.getApiParamEntityList().stream()
                .filter(p -> p.getParamType().equals(0))
                .collect(Collectors.toList());
        this.responseParams =
            taskConfig.getApiParamEntityList().stream()
                .filter(p -> p.getParamType().equals(1))
                .collect(Collectors.toList());

        this.dsType = guideTaskConfigInfo.getDsType();
        this.dsLink = guideTaskConfigInfo.getDsLink();
        this.username = guideTaskConfigInfo.getUsername();
        this.password = guideTaskConfigInfo.getPassword();
        this.tableName = guideTaskConfigInfo.getTableName();
    }

    private Pair<Integer, Integer> parsePage() throws Exception {

        // 1.若用户勾选了返回结果分页，则按照用户传入的分页参数，超过2000按2000计算
        // 2.若用户未勾选返回结果分页，即时用户传了分页参数也不生效，（pageSize按照默认2000计算，向导模式）
        boolean isPageable = (pageable != null && pageable == 1);

        int pageNum = 1, pageSize = DataApiCommomParam.DEFAULT_PAGESIZE;

        if (isPageable) {
            if (params.containsKey(DataApiCommomParam.PAGENUM)) {
                pageNum = Integer.parseInt(params.get(DataApiCommomParam.PAGENUM));
            }

            if (params.containsKey(DataApiCommomParam.PAGESIZE)) {
                pageSize = Integer.parseInt(params.get(DataApiCommomParam.PAGESIZE));
            }
        }

        pageSize = Math.min(pageSize, DataApiCommomParam.DEFAULT_PAGESIZE);
        pageSize = Math.max(pageSize, 1);

        return ImmutablePair.of(pageNum, pageSize);
    }

    private void convertResult(List<Map<String, Object>> resultData) {
        if (CollectionUtils.isEmpty(resultData)) {
            return;
        }

        int paramLen = responseParams.size();

        for (int i = 0; i < resultData.size(); i++) {
            Map<String, Object> objectMap = resultData.get(i);
            Map<String, Object> newObjectMap = new LinkedHashMap<>();

            int j = 0;
            for (Map.Entry<String, Object> entry : objectMap.entrySet()) {
                if (j >= paramLen) {
                    break;
                }
                newObjectMap.put(responseParams.get(j).getParamName(), entry.getValue());
                j++;
            }
            objectMap.clear();
            resultData.set(i, newObjectMap);
        }
    }

    private QueryBuilder getQueryBuilder() throws Exception {

        String jdbcDriverName = JDBCUtil.getJDBCDriver(dsType);

        Class.forName(jdbcDriverName);

        Database database = Database.from(dsType);

        Connection connection;
        if (Objects.equals(DSType.EsSQL.name(), dsType)) {
            // es sql特殊处理
            String[] urlArr = dsLink.split(",");
            if (urlArr.length == 0) {
                throw new RuntimeException("es集群尚未配置");
            }
            Properties properties = new Properties();
            properties.put("user", username);
            properties.put("password", password);
            String url = "jdbc:elasticsearch://" + urlArr[0].trim();
            connection = DriverManager.getConnection(url, properties);
        } else {
            connection = DriverManager.getConnection(dsLink, username, password);
        }
        QueryBuilder queryBuilder = new QueryBuilder(database, connection);

        return queryBuilder;
    }

    @Override
    protected Boolean checkTaskConfig() {
        Boolean checkPass = true;

        List<String> logs = Lists.newArrayList();

        if (StringUtils.isBlank(dsType)
            || StringUtils.isBlank(dsLink)
            || StringUtils.isBlank(username)
            || StringUtils.isBlank(password)) {
            String log =
                String.format(
                    "[%s] datasource config is incompelete",
                    DateFormatUtils.format(new Date(), "HH:mm:ss.SSS"));
            logs.add(log);
            checkPass = false;
        }

        if (StringUtils.isBlank(tableName)) {
            String log =
                String.format(
                    "[%s] table config is incompelete",
                    DateFormatUtils.format(new Date(), "HH:mm:ss.SSS"));
            logs.add(log);
            checkPass = false;
        }

        if (!checkPass) {
            DataApiUtil.writeLog(this.apiRecord, id, logs);
        }

        return checkPass;
    }

    @Override
    protected DataApiTaskResultDomain doRun() throws Exception {

        try {
            Pair<Integer, Integer> pagePair = parsePage();

            queryBuilder = getQueryBuilder();

            int pageNum = pagePair.getLeft(), pageSize = pagePair.getRight();

            Select select = this.buildSelect();

            From from = this.buildFrom(select);

            Where where = this.buildWhere(from);

            String log =
                String.format(
                    "[%s] api sql realsql: %s",
                    DateFormatUtils.format(new Date(), "HH:mm:ss.SSS"), where.toString());
            DataApiUtil.writeLog(this.apiRecord, id, Lists.newArrayList(log));

            Limit limit = this.buildLimit(where, pageNum, pageSize);

            log =
                String.format(
                    "[%s] query database started.", DateFormatUtils.format(new Date(), "HH:mm:ss.SSS"));
            DataApiUtil.writeLog(this.apiRecord, id, Lists.newArrayList(log));

            List<Map<String, Object>> resultData = limit.list(new MapRowMapper());
            convertResult(resultData);

            if (this.pageable == 0) {
                log =
                    String.format(
                        "[%s] query database finished.",
                        DateFormatUtils.format(new Date(), "HH:mm:ss.SSS"));
                DataApiUtil.writeLog(this.apiRecord, id, Lists.newArrayList(log));

                return DataApiTaskResultDomain.builder()
                    .status(DataApiCallStatusEnum.SUCCESS.getStatus())
                    .output(resultData)
                    .build();
            }

            queryBuilder = this.getQueryBuilder();

            From table = queryBuilder.reset().select().count("1").from().table(tableName);
            Long totalElements = this.buildWhere(table).single(new CountRowMapper());

            PageResult result = new PageResult(resultData, pageNum, pageSize, totalElements);

            log =
                String.format(
                    "[%s] query database finished.", DateFormatUtils.format(new Date(), "HH:mm:ss.SSS"));
            DataApiUtil.writeLog(this.apiRecord, id, Lists.newArrayList(log));

            return DataApiTaskResultDomain.builder()
                .status(DataApiCallStatusEnum.SUCCESS.getStatus())
                .output(result)
                .build();

        } finally {
            if (null != queryBuilder) {
                queryBuilder.close();
            }
        }
    }

    private Select buildSelect() {

        List<String> columns = Lists.newLinkedList();

        String quote = "";
        if ("MySQL".equalsIgnoreCase(dsType) || DSType.EsSQL.name().equalsIgnoreCase(dsType)) {
            quote = "`";
        }
        if (DSType.PostgreSQL.toString().equalsIgnoreCase(dsType)) {
            quote = "\"";
        }
        if (CollectionUtils.isNotEmpty(responseParams)) {
            for (DataApiTaskParamDomain responseParam : responseParams) {
                String columnName = responseParam.getBindColumnName();

                columns.add(
                    String.format(
                        "%s%s%s as %s%s%s",
                        quote, columnName, quote, quote, responseParam.getParamName(), quote));
            }
        }

        Select select = queryBuilder.select().columns(columns.toArray(new String[columns.size()]));
        return select;
    }

    private From buildFrom(Select select) {
        From from = select.from().table(tableName);
        return from;
    }

    private Where buildWhere(From from) {
        Where where = from.where();

        if (CollectionUtils.isNotEmpty(requestParams) && !params.isEmpty()) {

            Map<DataApiTaskParamDomain, Object> paramsMap =
                new DataApiParamConverter().convertParams(requestParams, params);

            if (paramsMap != null && !paramsMap.isEmpty()) {
                paramsMap.forEach(
                    (paramConfig, paramValue) -> {
                        String columnName = paramConfig.getBindColumnName();
                        Integer paramOperator = paramConfig.getParamOperator();
                        if (paramValue == null || paramValue == DataApiParamConverter.EMPTY) {
                            return;
                        }
                        if (paramOperator == null || paramOperator == 0) {
                            where.and(String.format("%s=?", columnName), paramValue);
                        } else if (paramOperator == 1) {
                            where.and(String.format("%s like ?", columnName), "%" + paramValue + "%");
                        } else if (paramOperator == 2) {
                            Object[] arr =
                                (paramValue instanceof Collection)
                                    ? ((Collection<Object>) paramValue).toArray(new Object[0])
                                    : new Object[]{paramValue};
                            where.andIn(columnName, arr);
                        } else if (paramOperator == 3) { // between
                            Object[] arr =
                                (paramValue instanceof Collection)
                                    ? ((Collection<Object>) paramValue).toArray(new Object[0])
                                    : new Object[]{paramValue};
                            if (arr.length < 2) {
                                throw new BusinessException("表达式错误");
                            }
                            where.andBetween(columnName, arr[0], arr[1]);

                        } else if (paramOperator == 4) { // >
                            where.and(String.format("%s>?", columnName), paramValue);
                        } else if (paramOperator == 5) { // <
                            where.and(String.format("%s<?", columnName), paramValue);
                        } else if (paramOperator == 6) { // >=
                            where.and(String.format("%s>=?", columnName), paramValue);
                        } else if (paramOperator == 7) { // <=
                            where.and(String.format("%s<=?", columnName), paramValue);
                        } else if (paramOperator == 8) { // !=
                            where.and(String.format("%s!=?", columnName), paramValue);
                        } else if (paramOperator == 9) {
                            where.and(String.format("%s not like ?", columnName), "%" + paramValue + "%");
                        } else if (paramOperator == 10) {
                            Object[] arr =
                                (paramValue instanceof Collection)
                                    ? ((Collection<Object>) paramValue).toArray(new Object[0])
                                    : new Object[]{paramValue};
                            where.andNotIn(columnName, arr);
                        }
                    });
            }
        }
        return where;
    }

    private Limit buildLimit(Where where, int pageNum, int pageSize) {
        // 系统没有设计指定排序字段的功能，默认取响应字段集合的第一个作为排序字段
        Database database = Database.from(dsType);

        OrderBy orderBy = null;
        if (database == Database.SQLSERVER) {
            orderBy = where.orderBy(responseParams.get(0).getBindColumnName());
        }
        Limit limit =
            (orderBy != null)
                ? orderBy.limit((pageNum - 1) * pageSize, pageSize)
                : where.limit((pageNum - 1) * pageSize, pageSize);
        return limit;
    }
}
